/* This file is part of jpcsp. Jpcsp is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Jpcsp is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Jpcsp. If not, see <http://www.gnu.org/licenses/>. */ package jpcsp.GUI; import javax.sound.sampled.AudioFormat; import javax.sound.sampled.AudioSystem; import javax.sound.sampled.DataLine; import javax.sound.sampled.LineUnavailableException; import javax.sound.sampled.SourceDataLine; import jpcsp.Memory; import jpcsp.MemoryMap; import jpcsp.HLE.Modules; import jpcsp.HLE.VFS.IVirtualFile; import jpcsp.HLE.modules.SysMemUserForUser; import jpcsp.HLE.modules.SysMemUserForUser.SysMemInfo; import jpcsp.HLE.modules.sceAtrac3plus; import jpcsp.HLE.modules.sceAtrac3plus.AtracFileInfo; import jpcsp.media.codec.CodecFactory; import jpcsp.media.codec.ICodec; import jpcsp.memory.IMemoryWriter; import jpcsp.memory.MemoryWriter; import jpcsp.util.Utilities; public class UmdBrowserSound { private boolean done; private boolean threadExit; private Memory mem; private SysMemInfo memInfo; private int samplesAddr; private int inputAddr; private int inputLength; private int inputPosition; private int inputOffset; private int inputBytesPerFrame; private int channels; private ICodec codec; private SourceDataLine mLine; private class SoundPlayThread extends Thread { @Override public void run() { while (!done) { stepSound(); } threadExit = true; } } public UmdBrowserSound(Memory mem, byte[] data) { initMemory(mem); if (read(data)) { startThread(); } else { threadExit = true; } } public UmdBrowserSound(Memory mem, IVirtualFile vFile, int codecType, AtracFileInfo atracFileInfo) { initMemory(mem); byte[] audioData = Utilities.readCompleteFile(vFile); int atracBytesPerFrame = (((audioData[2] & 0x03) << 8) | ((audioData[3] & 0xFF) << 3)) + 8; int headerLength = 8; inputLength = 0; for (int i = 0; i < audioData.length; i += headerLength + atracBytesPerFrame) { write(mem, inputAddr + inputLength, audioData, i + headerLength, atracBytesPerFrame); inputLength += atracBytesPerFrame; } atracFileInfo.atracBytesPerFrame = atracBytesPerFrame; if (read(codecType, atracFileInfo)) { startThread(); } else { threadExit = true; } } private void initMemory(Memory mem) { this.mem = mem; memInfo = Modules.SysMemUserForUserModule.malloc(SysMemUserForUser.KERNEL_PARTITION_ID, "UmdBrowserSound", SysMemUserForUser.PSP_SMEM_Low, 0x20000, 0); if (memInfo != null) { samplesAddr = memInfo.addr; inputAddr = memInfo.addr + 0x10000; } else { // PSP not yet initialized, use any memory space samplesAddr = MemoryMap.START_USERSPACE; inputAddr = MemoryMap.START_USERSPACE + 0x10000; } } public void stopSound() { done = true; while (!threadExit) { Utilities.sleep(1, 0); } if (mLine != null) { mLine.close(); } if (memInfo != null) { Modules.SysMemUserForUserModule.free(memInfo); memInfo = null; samplesAddr = 0; inputAddr = 0; } } private static void write(Memory mem, int addr, byte[] data, int offset, int length) { length = Math.min(length, data.length - offset); for (int i = 0; i < length; i++) { mem.write8(addr + i, data[offset + i]); } } private void startThread() { Thread soundPlayThread = new SoundPlayThread(); soundPlayThread.setDaemon(true); soundPlayThread.setName("Umd Browser Sound Play Thread"); soundPlayThread.start(); } private boolean read(byte[] data) { if (data == null || data.length == 0) { return false; } inputLength = data.length; IMemoryWriter memoryWriter = MemoryWriter.getMemoryWriter(inputAddr, inputLength, 1); for (int i = 0; i < data.length; i++) { memoryWriter.writeNext(data[i] & 0xFF); } memoryWriter.flush(); AtracFileInfo atracFileInfo = new AtracFileInfo(); int codecType = sceAtrac3plus.analyzeRiffFile(mem, inputAddr, inputLength, atracFileInfo); if (codecType < 0) { return false; } boolean result = read(codecType, atracFileInfo); return result; } private boolean read(int codecType, AtracFileInfo atracFileInfo) { codec = CodecFactory.getCodec(codecType); if (codec == null) { return false; } int result = codec.init(atracFileInfo.atracBytesPerFrame, atracFileInfo.atracChannels, atracFileInfo.atracChannels, atracFileInfo.atracCodingMode); if (result < 0) { return false; } AudioFormat audioFormat = new AudioFormat(44100, 16, atracFileInfo.atracChannels, true, false); DataLine.Info info = new DataLine.Info(SourceDataLine.class, audioFormat); try { mLine = (SourceDataLine) AudioSystem.getLine(info); mLine.open(audioFormat); } catch (LineUnavailableException e) { return false; } mLine.start(); inputOffset = atracFileInfo.inputFileDataOffset; inputPosition = inputOffset; inputBytesPerFrame = atracFileInfo.atracBytesPerFrame; channels = atracFileInfo.atracChannels; return true; } private boolean stepSound() { if (inputPosition + inputBytesPerFrame >= inputLength) { // Loop sound inputPosition = inputOffset; } int result = codec.decode(inputAddr + inputPosition, inputBytesPerFrame, samplesAddr); if (result < 0) { return false; } inputPosition += inputBytesPerFrame; byte bytes[] = new byte[codec.getNumberOfSamples() * 2 * channels]; for (int i = 0; i < bytes.length; i++) { bytes[i] = (byte) mem.read8(samplesAddr + i); } mLine.write(bytes, 0, bytes.length); return true; } }